{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4.3 PyTorchでMNIST"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 手書き数字の画像データMNISTをダウンロード\n",
    "\n",
    "# from sklearn.datasets import fetch_mldata\n",
    "# mnist = fetch_mldata('MNIST original', data_home=\".\")  # data_homeは保存先を指定します\n",
    "\n",
    "# 2019年1月31日訂正\n",
    "# 上記コードでは、以下のエラーが発生します\n",
    "#  [WinError 10060] 接続済みの呼び出し先が一定の時間を過ぎても正しく応答しなかったため、接続できませんでした。または接続済みのホストが応答しなかったため、確立された接続は失敗しました。\n",
    "\n",
    "from sklearn.datasets import fetch_openml\n",
    "mnist = fetch_openml('mnist_784', version=1, data_home=\".\")  # data_homeは保存先を指定します\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 1. データの前処理（画像データとラベルに分割し、正規化）\n",
    "\n",
    "X = mnist.data / 255  # 0-255を0-1に正規化\n",
    "y = mnist.target\n",
    "\n",
    "# 2019年1月31日訂正\n",
    "# MNISTのデータセットの変更により、ラベルが数値データになっていないので、\n",
    "# 以下により、NumPyの配列の数値型に変換します\n",
    "\n",
    "import numpy as np\n",
    "y = np.array(y)\n",
    "y = y.astype(np.int32)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "# MNISTのデータの1つ目を可視化する\n",
    "\n",
    "# 2019年1月31日訂正\n",
    "# %matplotlibで%のあとに入っていたスペースを削除\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "\n",
    "plt.imshow(X[0].reshape(28, 28), cmap='gray')\n",
    "print(\"この画像データのラベルは{:.0f}です\".format(y[0]))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 2. DataLoderの作成\n",
    "\n",
    "import torch\n",
    "from torch.utils.data import TensorDataset, DataLoader\n",
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "# 2.1 データを訓練とテストに分割（6:1）\n",
    "X_train, X_test, y_train, y_test = train_test_split(\n",
    "    X, y, test_size=1/7, random_state=0)\n",
    "\n",
    "# 2.2 データをPyTorchのTensorに変換\n",
    "X_train = torch.Tensor(X_train)\n",
    "X_test = torch.Tensor(X_test)\n",
    "y_train = torch.LongTensor(y_train)\n",
    "y_test = torch.LongTensor(y_test)\n",
    "\n",
    "# 2.3 データとラベルをセットにしたDatasetを作成\n",
    "ds_train = TensorDataset(X_train, y_train)\n",
    "ds_test = TensorDataset(X_test, y_test)\n",
    "\n",
    "# 2.4 データセットのミニバッチサイズを指定した、Dataloaderを作成\n",
    "# Chainerのiterators.SerialIteratorと似ている\n",
    "loader_train = DataLoader(ds_train, batch_size=64, shuffle=True)\n",
    "loader_test = DataLoader(ds_test, batch_size=64, shuffle=False)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 3. ネットワークの構築\n",
    "# Keras風の書き方 \n",
    "\n",
    "from torch import nn\n",
    "\n",
    "model = nn.Sequential()\n",
    "model.add_module('fc1', nn.Linear(28*28*1, 100))\n",
    "model.add_module('relu1', nn.ReLU())\n",
    "model.add_module('fc2', nn.Linear(100, 100))\n",
    "model.add_module('relu2', nn.ReLU())\n",
    "model.add_module('fc3', nn.Linear(100, 10))\n",
    "\n",
    "print(model)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 4. 誤差関数と最適化手法の設定\n",
    "\n",
    "from torch import optim\n",
    "\n",
    "# 誤差関数の設定\n",
    "loss_fn = nn.CrossEntropyLoss()  # 変数名にはcriterionが使われることも多い\n",
    "\n",
    "# 重みを学習する際の最適化手法の選択\n",
    "optimizer = optim.Adam(model.parameters(), lr=0.01)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 5. 学習と推論の設定\n",
    "# 5-1. 学習1回でやることを定義します\n",
    "# Chainerのtraining.Trainer()に対応するものはない\n",
    "\n",
    "\n",
    "def train(epoch):\n",
    "    model.train()  # ネットワークを学習モードに切り替える\n",
    "\n",
    "    # データローダーから1ミニバッチずつ取り出して計算する\n",
    "    for data, targets in loader_train:\n",
    "      \n",
    "        optimizer.zero_grad()  # 一度計算された勾配結果を0にリセット\n",
    "        outputs = model(data)  # 入力dataをinputし、出力を求める\n",
    "        loss = loss_fn(outputs, targets)  # 出力と訓練データの正解との誤差を求める\n",
    "        loss.backward()  # 誤差のバックプロパゲーションを求める\n",
    "        optimizer.step()  # バックプロパゲーションの値で重みを更新する\n",
    "\n",
    "    print(\"epoch{}：終了\\n\".format(epoch))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 5. 学習と推論の設定\n",
    "# 5-2. 推論1回でやることを定義します\n",
    "# Chainerのtrainer.extend(extensions.Evaluator())に対応するものはない\n",
    "\n",
    "\n",
    "def test():\n",
    "    model.eval()  # ネットワークを推論モードに切り替える\n",
    "    correct = 0\n",
    "\n",
    "    # データローダーから1ミニバッチずつ取り出して計算する\n",
    "    with torch.no_grad():  # 微分は推論では必要ない\n",
    "        for data, targets in loader_test:\n",
    "\n",
    "            outputs = model(data)  # 入力dataをinputし、出力を求める\n",
    "\n",
    "            # 推論する\n",
    "            _, predicted = torch.max(outputs.data, 1)  # 確率が最大のラベルを求める\n",
    "            correct += predicted.eq(targets.data.view_as(predicted)).sum()  # 正解と一緒だったらカウントアップ\n",
    "\n",
    "    # 正解率を出力\n",
    "    data_num = len(loader_test.dataset)  # データの総数\n",
    "    print('\\nテストデータの正解率: {}/{} ({:.0f}%)\\n'.format(correct,\n",
    "                                                   data_num, 100. * correct / data_num))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 学習なしにテストデータで推論してみよう\n",
    "test()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 6. 学習と推論の実行\n",
    "for epoch in range(3):\n",
    "    train(epoch)\n",
    "\n",
    "test()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 例えば2018番目の画像データを推論してみる\n",
    "\n",
    "index = 2018\n",
    "\n",
    "model.eval()  # ネットワークを推論モードに切り替える\n",
    "data = X_test[index]\n",
    "output = model(data)  # 入力dataをinputし、出力を求める\n",
    "_, predicted = torch.max(output.data, 0)  # 確率が最大のラベルを求める\n",
    "\n",
    "print(\"予測結果は{}\".format(predicted))\n",
    "\n",
    "X_test_show = (X_test[index]).numpy()\n",
    "plt.imshow(X_test_show.reshape(28, 28), cmap='gray')\n",
    "print(\"この画像データの正解ラベルは{:.0f}です\".format(y_test[index]))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#-----------------------------------------------"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 3. ネットワークの構築\n",
    "# ニューラルネットワークの設定（Chainer風の書き方）\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "\n",
    "class Net(nn.Module):\n",
    "\n",
    "    def __init__(self, n_in, n_mid, n_out):\n",
    "        super(Net, self).__init__()\n",
    "        self.fc1 = nn.Linear(n_in, n_mid)  # Chainerと異なり、Noneは受けつけない\n",
    "        self.fc2 = nn.Linear(n_mid, n_mid)\n",
    "        self.fc3 = nn.Linear(n_mid, n_out)\n",
    "\n",
    "    def forward(self, x):\n",
    "        # 入力xに合わせてforwardの計算を変えられる\n",
    "        h1 = F.relu(self.fc1(x))\n",
    "        h2 = F.relu(self.fc2(h1))\n",
    "        output = self.fc3(h2)\n",
    "        return output\n",
    "\n",
    "\n",
    "model = Net(n_in=28*28*1, n_mid=100, n_out=10)  # ネットワークのオブジェクトを生成\n",
    "print(model)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(model)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
